package utils

import (
	"fmt"
	"github.com/gomodule/redigo/redis"
	"time"
)

type RedigoLock struct {
	Resource         string     //key
	Token            string     //value
	Conn             redis.Conn //redis pool
	KeyExistenceTime int        //nx key 存在时间
	MaxLockIndex     int
}

// TryLock 秒级锁
func (lock *RedigoLock) TryLock() (ok bool, err error) {
	_, err = redis.String(lock.Conn.Do("SET", lock.key(), lock.Token, "EX", lock.KeyExistenceTime, "NX"))
	if err == redis.ErrNil {
		// The lock was not successful, it already exists.
		index := 0
		if lock.MaxLockIndex == 0 {
			lock.MaxLockIndex = 30
		}
		if lock.KeyExistenceTime == 0 {
			lock.KeyExistenceTime = 5
		}
		for {
			_, err = redis.String(lock.Conn.Do("SET", lock.key(), lock.Token, "EX", lock.KeyExistenceTime, "NX"))
			if err == nil {
				return true, nil
			}
			time.Sleep(time.Millisecond * 300)
			index++
			if index > lock.MaxLockIndex {
				return false, nil
			}
		}
	}
	if err != nil {
		return false, err
	}
	return true, nil
}

func (lock *RedigoLock) Unlock() {
	s, _ := redis.String(lock.Conn.Do("get", lock.key()))
	//防止误删key
	if s == lock.Token {
		_, err := lock.Conn.Do("del", lock.key())
		if err != nil {
			fmt.Println("RedigoLock del err:", err.Error())
		}
	}
	defer lock.Conn.Close()
	return
}

func (lock *RedigoLock) key() string {
	return fmt.Sprintf("redislock:%s", lock.Resource)
}

func NewRedigoLock(resource, token string, keyExistenceTime, maxLockIndex int) *RedigoLock { //resource为key;token为value;keyExistenceTime为key存在时长时间单位s;maxLockIndex为最大尝试加锁次数,间隔时间300ms
	return &RedigoLock{
		Resource:         resource,
		Token:            token,
		Conn:             getRedisPool(1).Get(),
		KeyExistenceTime: keyExistenceTime,
		MaxLockIndex:     maxLockIndex,
	}
}

func getRedisPool(dbNum int) *redis.Pool {

	host := "RedisHost"
	port := "RedisPort"
	password := "RedisPassword"

	//连接地址
	RedisConn := fmt.Sprintf("%s:%d", host, port)
	//db分区
	RedisDbNum := dbNum
	//密码
	RedisPassword := password

	//建立连接池
	return &redis.Pool{

		//最大的空闲连接数，表示即使没有redis连接时依然可以保持N个空闲的连接，而不被清除，随时处于待命状态。
		MaxIdle: 100,
		//最大的激活连接数，表示同时最多有N个连接
		MaxActive: 2000,
		//最大的空闲连接等待时间，超过此时间后，空闲连接将被关闭
		IdleTimeout: 300 * time.Second,
		//建立连接
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", RedisConn)
			if err != nil {
				return nil, fmt.Errorf("redis connection error: %s", err)
			}
			if RedisPassword != "" {
				if _, authErr := c.Do("AUTH", RedisPassword); authErr != nil {
					return nil, fmt.Errorf("redis auth password error: %s", authErr)
				}
			}
			//选择分区
			c.Do("SELECT", RedisDbNum)
			return c, nil
		},
		//ping
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("PING")
			if err != nil {
				return fmt.Errorf("ping redis error: %s", err)
			}
			return nil
		},
	}

}
